<template>
  <div class="file-upload">
    <div
      class="upload-area"
      :class="{ 'is-dragover': drag && isDragOver }"
      v-on="events"
    >
      <slot v-if="isUploading" name="loading">
        <button disabled>正在上传</button>
      </slot>
      <slot
        name="uploaded"
        v-else-if="lastFileData && lastFileData.loaded"
        :uploadedData="lastFileData.data"
      >
        <button>点击上传</button>
      </slot>
      <slot v-else name="default">
        <button>点击上传</button>
      </slot>
    </div>
    <input
      ref="fileInput"
      type="file"
      :style="{ display: 'none' }"
      @change="handleFileChange"
    />
    <ul class="upload-list" v-if="showUploadList">
      <li
        v-for="file in filesList"
        :key="file.uid"
        :class="`uploaded-file upload-${file.status}`"
      >
        <img
          v-if="file.url && listType === 'picture'"
          class="upload-list-thumbnail"
          :src="file.url"
          :alt="file.name"
        />
        <span v-if="file.status === 'loading'" class="file-icon">
          <LoadingOutlined />
        </span>
        <span v-else class="file-icon"><FileOutlined /></span>
        <span class="filename">{{ file.name }}</span>
        <button class="delete-icon" @click="removeFile(file.uid)">
          <DeleteOutlined />
        </button>
      </li>
    </ul>
  </div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType, reactive, ref } from "vue";
import {
  DeleteOutlined,
  LoadingOutlined,
  FileOutlined,
} from "@ant-design/icons-vue";
import { v4 as uuidv4 } from "uuid";
import axios from "axios";
import { last } from "lodash-es";
type UploadStatus = "ready" | "loading" | "success" | "error";
type FileListType = "picture" | "text";
type CheckUpload = (file: File) => boolean | Promise<File>;

export interface UploadFile {
  uid: string;
  size: number;
  name: string;
  status: UploadStatus;
  raw: File;
  resp?: any;
  url?: string;
}

export default defineComponent({
  name: "uploader",
  components: {
    DeleteOutlined,
    LoadingOutlined,
    FileOutlined,
  },
  props: {
    action: {
      type: String,
      required: true,
    },
    beforeUpload: {
      type: Function as PropType<CheckUpload>,
    },
    drag: {
      type: Boolean,
      default: false,
    },
    autoUpload: {
      type: Boolean,
      default: true,
    },
    listType: {
      type: String as PropType<FileListType>,
      default: "text",
    },
    showUploadList: {
      type: Boolean,
      default: true,
    },
  },
  emits: ["success", "error", "change"],
  setup(props, { emit }) {
    const fileInput = ref<null | HTMLInputElement>(null);
    const filesList = ref<UploadFile[]>([]);
    const isDragOver = ref(false);
    const isUploading = computed(() => {
      return filesList.value.some((file) => file.status === "loading");
    });
    const lastFileData = computed(() => {
      const lastFile = last(filesList.value);
      if (lastFile) {
        return {
          loaded: lastFile.status === "success",
          data: lastFile.resp,
        };
      }
      return false;
    });
    const removeFile = (id: string) => {
      filesList.value = filesList.value.filter((file) => file.uid !== id);
    };
    const triggerUpload = () => {
      if (fileInput.value) {
        fileInput.value.click();
      }
    };
    const postFile = (readyFile: UploadFile) => {
      const formData = new FormData();
      formData.append(readyFile.name, readyFile.raw);
      readyFile.status = "loading";
      // const fileObj = reactive<UploadFile>({
      //   uid: uuidv4(),
      //   size: uploadedFile.size,
      //   name: uploadedFile.name,
      //   status: "loading",
      //   raw: uploadedFile,
      // });
      // filesList.value.push(fileObj);
      axios
        .post(props.action, formData, {
          headers: {
            "Content-Type": "multipart/form-data",
          },
        })
        .then((resp) => {
          readyFile.resp = resp.data.data.urls;
          emit("success", {
            resp: resp.data,
            file: readyFile,
            list: filesList.value,
          });
        })
        .catch((e: any) => {
          readyFile.status = "error";
          emit("error", { error: e, file: readyFile, list: filesList.value });
        })
        .finally(() => {
          if (readyFile.status !== "error") readyFile.status = "success";
          if (fileInput.value) {
            fileInput.value.value = "";
          }
        });
    };

    const addFileToList = (uploadedFile: File) => {
      const fileObj = reactive<UploadFile>({
        uid: uuidv4(),
        size: uploadedFile.size,
        name: uploadedFile.name,
        status: "ready",
        raw: uploadedFile,
      });
      if (props.listType === "picture") {
        try {
          console.log("11");
          console.log(uploadedFile);
          console.log("11");
          // 预览本地图片
          fileObj.url = URL.createObjectURL(uploadedFile);
          // 方法 1 fileObj.url = URL.createObjectURL(uploadedFile); 指向 内存中地址 自动遵循内存回收机制 自动回收
          // 方法 2
          // 返回 base64  可以直接在项目内使用 存储于 document 内， 需要 unload 或者 revokeObjectUrl() 手动清除
          // const fileReader = new FileReader();
          // fileReader.readAsDataURL(uploadedFile);
          // fileReader.addEventListener("load", () => {
          //   fileObj.url = fileReader.result as string;
          // });
        } catch (e) {
          console.error("upload File error", e);
        }
      }
      filesList.value.push(fileObj);
      if (props.autoUpload) {
        postFile(fileObj);
      }
    };

    const beforeUploadCheck = (files: null | FileList) => {
      if (files) {
        const uploadedFile = files[0];
        if (props.beforeUpload) {
          const result = props.beforeUpload(uploadedFile);
          if (result && result instanceof Promise) {
            result
              .then((processedFile) => {
                if (processedFile instanceof File) {
                  addFileToList(processedFile);
                } else {
                  throw new Error("beforeUpload Promise 应为 File object");
                }
              })
              .catch((e) => {
                console.error(e);
              });
          } else if (result === true) {
            addFileToList(uploadedFile);
          }
        } else {
          addFileToList(uploadedFile);
        }
      }
    };

    const uploadFiles = () => {
      filesList.value
        .filter((file) => file.status === "ready")
        .forEach((readyFile) => postFile(readyFile));
    };

    let events: { [ke: string]: (e: any) => void } = {
      click: triggerUpload,
    };
    const handleFileChange = (e: Event) => {
      const target = e.target as HTMLInputElement;
      beforeUploadCheck(target.files);
    };

    const handleDrag = (e: DragEvent, over: boolean) => {
      e.preventDefault();
      isDragOver.value = over;
    };
    const handleDrop = (e: DragEvent) => {
      e.preventDefault();
      isDragOver.value = false;
      if (e.dataTransfer) {
        beforeUploadCheck(e.dataTransfer.files);
      }
    };

    if (props.drag) {
      events = {
        ...events,
        dragover: (e: DragEvent) => {
          handleDrag(e, true);
        },
        dragleave: (e: DragEvent) => {
          handleDrag(e, false);
        },
        drop: handleDrop,
      };
    }
    return {
      fileInput,
      filesList,
      isUploading,
      triggerUpload,
      handleFileChange,
      removeFile,
      lastFileData,
      isDragOver,
      handleDrag,
      handleDrop,
      events,
      uploadFiles,
    };
  },
});
</script>

<style lang="scss" scoped>
.file-upload {
  .upload-list {
    margin: 0;
    padding: 0;
    list-style-type: none;
    li {
      transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
      font-size: 14px;
      line-height: 1.8;
      margin-top: 5px;
      box-sizing: border-box;
      border-radius: 4px;
      min-width: 200px;
      position: relative;
      &:first-child {
        margin-top: 10px;
      }
      .upload-list-thumbnail {
        vertical-align: middle;
        display: inline-block;
        width: 70px;
        height: 70px;
        position: relative;
        z-index: 1;
        background-color: #fff;
        object-fit: cover;
      }
      .file-icon {
        svg {
          margin-right: 5px;
          color: rgba(0, 0, 0, 0.45);
        }
      }
      .filename {
        margin-left: 5px;
        margin-right: 40px;
      }
      &.upload-error {
        color: #f5222d;
        svg {
          color: #f5222d;
        }
      }
      .file-status {
        display: block;
        position: absolute;
        right: 5px;
        top: 0;
        line-height: inherit;
      }
      .delete-icon {
        display: none;
        position: absolute;
        right: 7px;
        top: 0;
        line-height: inherit;
        cursor: pointer;
      }
      &:hover {
        background-color: #efefef;
        .file-status {
          display: none;
        }
        .delete-icon {
          display: block;
        }
      }
    }
  }
}
</style>
